<template>
  <el-card shadow="never" class="rd-8 mb-8">
    <template #header>
      <div class="card-header f-c-c">{{ showMsg }}表单</div>
    </template>
    <el-steps :active="1" finish-status="success" align-center class="mb-40">
      <el-step title="课程基本信息" />
      <el-step title="章节基本信息" />
      <el-step title="发布课程" />
    </el-steps>
    <el-button type="primary" class="mb-40" icon="Plus" @click="addChapterDiag">
      添加章节
    </el-button>
    <div class="chapter">
      <div
        class="chapter_item"
        v-for="(chapter, chapterIndex) in chapterSectionList"
        :key="chapter.chapterId"
      >
        <!-- 章节 -->
        <div class="chapter_title">
          <div class="catalog_num">
            <span>
              <i>
                <em style="font-style: normal">{{ chapterIndex + 1 }}</em>
              </i>
            </span>
          </div>
          <div class="catalog_name flex-grow-1">
            <div class="clicktitle">
              <span :title="chapter.title">
                {{ chapter.title }}
              </span>
            </div>
          </div>
          <div>
            <el-button
              text
              type="primary"
              icon="Plus"
              @click="hadnleAddSection(chapter.chapterId)"
            >
              添加课时
            </el-button>
            <el-button
              text
              type="primary"
              icon="Edit"
              @click="editChapterDiag(chapter)"
            >
              编辑
            </el-button>
            <el-button
              text
              type="danger"
              icon="Delete"
              @click="handleDelChapter(chapter)"
            >
              删除
            </el-button>
          </div>
        </div>
        <!-- 课时 -->
        <div class="section">
          <ul>
            <li
              v-for="(section, sectionIndex) in chapter.sections"
              :key="section.sectionId"
            >
              <div class="catalog_name">
                <div class="clicktitle">
                  <span class="pr-5">
                    {{ `${chapterIndex + 1}.${sectionIndex + 1}` }}
                  </span>
                  <span :title="section.title">{{ section.title }}</span>
                </div>
                <el-button
                  text
                  type="primary"
                  icon="Edit"
                  @click="handleEditSection(section)"
                >
                  编辑
                </el-button>
                <el-button
                  text
                  type="danger"
                  icon="Delete"
                  @click="handleDelSection(section)"
                >
                  删除
                </el-button>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <div class="flex justify-center mb-20">
      <el-button @click="$router.push(`/course/edit/${route.params.courseId}`)">
        上一步
      </el-button>
      <el-button
        type="primary"
        @click="$router.push(`/course/publish/${route.params.courseId}`)"
      >
        下一步
      </el-button>
    </div>
  </el-card>
  <!-- 章节对话框 -->
  <el-dialog
    :title="chapterDialog.title"
    v-model="chapterDialog.visible"
    width="700px"
    align-center
    append-to-body
  >
    <el-form
      :model="chapterForm"
      :rules="chapterRules"
      ref="chapterFormRef"
      label-width="80px"
    >
      <el-form-item label="章节标题" prop="title">
        <el-input
          v-model="chapterForm.title"
          placeholder="请输入章节标题"
          maxlength="50"
        />
      </el-form-item>
      <el-form-item label="排序" prop="sort">
        <el-input-number
          v-model="chapterForm.sort"
          :min="1"
          placeholder="排序"
        />
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="cancelChapter()">取 消</el-button>
        <el-button type="primary" @click="submitChapterForm">确 定</el-button>
      </div>
    </template>
  </el-dialog>
  <!-- 课时对话框 -->
  <el-dialog
    :title="sectionDialog.title"
    v-model="sectionDialog.visible"
    width="700px"
    align-center
    append-to-body
    :before-close="cancelSection"
  >
    <el-form
      :model="sectionForm"
      :rules="sectionRules"
      ref="sectionFormRef"
      label-width="80px"
    >
      <el-form-item label="课时标题" prop="title">
        <el-input
          v-model="sectionForm.title"
          placeholder="请输入课时标题"
          maxlength="50"
        />
      </el-form-item>
      <el-form-item label="排序" prop="sort">
        <el-input-number
          v-model="sectionForm.sort"
          :min="1"
          placeholder="排序"
        />
      </el-form-item>
      <el-form-item label="视频" prop="videoId">
        <div class="flex-grow-1">
          <el-upload
            v-model:file-list="vodList"
            accept=".mp4, .flv, .avi, .wmv, .mov, .webm, .mpeg4, .mpg, .rm, .rmvb, .mkv, .m4v"
            :limit="1"
            action="/"
            multiple
            ref="uploadRef"
            :on-success="handleFileSuccess"
            :http-request="handleHttpRequest"
            :on-remove="handleRemoveFile"
          >
            <el-button type="primary">上传视频</el-button>
            <template #file="file">
              <div
                class="flex items-center justify-between"
                v-on:mouseover="videoType = `Delete`"
                v-on:mouseleave="videoType = `CircleCheck`"
              >
                <!-- {{ file }} -->
                <video width="500" controls ref="videoRef">
                  <!-- 设置视频URL -->
                  Your browser does not support the video tag.
                </video>
                <el-button
                  text
                  :icon="videoType"
                  :type="videoType == `CircleCheck` ? 'primary' : 'danger'"
                  @click="handleRemove(file)"
                ></el-button>
              </div>
            </template>
          </el-upload>
        </div>
      </el-form-item>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="cancelSection()">取 消</el-button>
        <el-button
          type="primary"
          @click="submitSectionForm"
          :loading="isUploadingVod"
        >
          <template #loading>
            <div class="custom-loading">
              <svg class="circular" viewBox="-10, -10, 50, 50">
                <path
                  class="path"
                  d="
            M 30 15
            L 28 17
            M 25.61 25.61
            A 15 15, 0, 0, 1, 15 30
            A 15 15, 0, 1, 1, 27.99 7.5
            L 15 15
          "
                  style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"
                />
              </svg>
            </div>
          </template>
          确 定
        </el-button>
      </div>
    </template>
  </el-dialog>
</template>

<script setup lang="ts">
import {
  reqAddChapter,
  reqChapterAndSectionVo,
  reqDelChapter,
  reqUpdateChapter,
} from '@/api/chapter'
import { Chapter, ChapterInfoVo } from '@/api/chapter/type'
import { reqAddSection, reqDelSection, reqUpdateSection } from '@/api/section'
import { Section } from '@/api/section/type'
import {
  reqCreateTask,
  reqDeleteVideo,
  reqMerge,
  reqPreSignUrl,
  reqTaskInfo,
} from '@/api/vod'
import { TaskRecord, Video } from '@/api/vod/type'
import md5 from '@/utils/md5'
import { httpExtra } from '@/utils/request'
import type {
  FormInstance,
  FormRules,
  UploadFile,
  UploadInstance,
  UploadRawFile,
  UploadRequestOptions,
} from 'element-plus'
import { ElNotification } from 'element-plus'
import Queue from 'promise-queue-plus'
import { useRoute } from 'vue-router'
let showMsg = computed(() => (chapterForm.value.courseId ? '编辑' : '添加'))
let route = useRoute()
let videoType = ref<string>('CircleCheck')
/* 查询章节课时列表 */
let chapterSectionList = ref<ChapterInfoVo[]>([])

const getChapterInfo = async (courseId: string) => {
  let { data } = await reqChapterAndSectionVo(courseId)
  chapterSectionList.value = data
}

/*章节diag*/
// #region
let chapterDialog = reactive<DialogOption>({
  title: '',
  visible: false,
})
const initChapterForm: Chapter = {
  chapterId: undefined,
  courseId: '',
  title: '',
  sort: undefined,
}

let chapterForm = ref<Chapter>({ ...initChapterForm })
const chapterFormRef = ref<FormInstance>()

const resetChapterForm = () => {
  chapterFormRef.value?.clearValidate()
  chapterForm.value = { ...initChapterForm }
}
const addChapterDiag = () => {
  resetChapterForm()
  chapterDialog.visible = true
  chapterDialog.title = '添加章节'
}

const editChapterDiag = (chapter: ChapterInfoVo) => {
  chapterForm.value = { ...chapter }
  chapterDialog.visible = true
  chapterDialog.title = '编辑章节'
}

const handleDelChapter = async (chapter: ChapterInfoVo) => {
  try {
    await ElMessageBox.confirm(
      '是否确认删除章节为"' + chapter.title + '"的数据项？',
      '系统提示',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      },
    )
    await reqDelChapter(chapter.chapterId)
    ElMessage.success('删除成功')
    await getChapterInfo(route.params.courseId as string)
  } catch (error) {
    return
  }
}

const cancelChapter = () => {
  resetChapterForm()
  chapterDialog.visible = false
}

const submitChapterForm = async () => {
  chapterForm.value.courseId = route.params.courseId as string
  chapterForm.value.chapterId
    ? await reqUpdateChapter(chapterForm.value)
    : await reqAddChapter(chapterForm.value)
  chapterDialog.visible = false
  ElMessage.success('操作成功')
  await getChapterInfo(route.params.courseId as string)
}

const chapterRules = reactive<FormRules>({
  title: [{ required: true, message: '章节标题不能为空', trigger: 'blur' }],
  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
})
// #endregion

/* 课时diag */
// #region
let vodList = ref<any[]>([])
let isUploadingVod = ref<boolean>(false)
let sectionDialog = reactive<DialogOption>({
  title: '',
  visible: false,
  chapterId: undefined,
})
const initSectionForm: Section = {
  sectionId: undefined,
  courseId: '',
  chapterId: '',
  title: '',
  sort: undefined,
  videoId: undefined,
  videoUrl: undefined,
  objectKey: '',
  bucketName: '',
}
const sectionRules = reactive<FormRules>({
  title: [{ required: true, message: '课时标题不能为空', trigger: 'blur' }],
  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }],
})

let sectionForm = ref<Section>({ ...initSectionForm })
const sectionFormRef = ref<FormInstance>()

const resetSectionForm = () => {
  sectionFormRef.value?.clearValidate()
  sectionForm.value = { ...initSectionForm }
}
const cancelSection = () => {
  resetSectionForm()
  vodList.value = []
  sectionDialog.visible = false
}
const submitSectionForm = async () => {
  if (sectionForm.value.sectionId) {
    await reqUpdateSection(sectionForm.value)
  } else {
    sectionForm.value.courseId = route.params.courseId as string
    sectionForm.value.chapterId = sectionDialog.chapterId
    await reqAddSection(sectionForm.value)
  }
  ElMessage.success('操作成功')
  sectionDialog.visible = false
  await getChapterInfo(route.params.courseId as string)
}

const hadnleAddSection = (chapterId: string) => {
  sectionDialog.title = '添加课时'
  sectionDialog.visible = true
  sectionDialog.chapterId = chapterId
}
const handleEditSection = (section: Section) => {
  sectionDialog.title = '修改课时'
  sectionDialog.visible = true
  sectionForm.value = section
  if (section.videoId) {
    vodList.value[0] = {
      status: 'success',
      name: section.videoUrl,
      uid: section.videoId,
      response: section.videoUrl,
    }

    nextTick(() => {
      videoRef.value.src = section.videoUrl
    })
  }
  // if delete video update vodDelObj
  vodDelObj = {
    videoId: section.videoId as string,
    bucketName: section.bucketName,
    objectKey: section.objectKey,
    videoUrl: section.videoUrl as string,
  } as any
}
const handleDelSection = async (section: Section) => {
  try {
    await ElMessageBox.confirm(
      '是否确认删除课时为"' + section.title + '"的数据项？',
      '系统提示',
      {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      },
    )
    await reqDelSection(section)
    ElMessage.success('删除成功')
    await getChapterInfo(route.params.courseId as string)
  } catch (error) {
    return
  }
}

// #endregion

const init = () => {
  if (route.params && route.params.courseId) {
    getChapterInfo(route.params.courseId as string) //调用根据 id查询的方法
  }
}
watch(
  () => route.params.courseId,
  () => init(),
  { immediate: true },
)

/**
 * 视频分片上传-------------------
 */
// #region
//上传的视频对象
let vodDelObj: Video = {
  videoId: '',
  uploadId: '',
  fileIdentifier: '',
  fileName: '',
  bucketName: '',
  objectKey: '',
  totalSize: 0,
  chunkSize: 0,
  chunkNum: 0,
  videoUrl: '',
}
// 文件上传分块任务的队列（用于移除文件时，停止该文件的上传队列） key：fileUid value： queue object
const fileUploadChunkQueue: any = {}
const uploadRef = ref<UploadInstance>()
const videoRef = ref()
/**
 * 2.获取一个上传任务，没有则初始化一个
 */
const getTaskInfo = async (file: UploadRawFile) => {
  let task
  let msg
  // 生成文件md5值
  const identifier = await md5(file)
  try {
    //获取上传进度
    const { data, message } = await reqTaskInfo(identifier as string)
    task = data
    // 返回文件内容
    msg = message
    if (!task) {
      //创建上传任务
      const { data, message } = await reqCreateTask(
        identifier as string,
        file.name,
        file.size,
        5 * 1024 * 1024,
      )
      msg = message
      task = data
      //更新待删除的视频数据
      Object.assign(vodDelObj, data.taskRecord)
      //上传完视频更新视频id
      sectionForm.value.videoId = data.taskRecord.videoId
    } else {
      Object.assign(vodDelObj, data.taskRecord)
      //上传完视频更新视频id
      sectionForm.value.videoId = data.taskRecord.videoId
    }
  } catch (error) {
    console.log(error)
    ElNotification.error({
      title: '文件上传错误',
      message: msg,
    })
  }
  return task
}

/**
 * 3.上传逻辑处理，如果文件已经上传完成（完成分块合并操作），则不会进入到此方法中
 */
const handleUpload = (
  file: UploadRawFile,
  taskRecord: TaskRecord,
  options: UploadRequestOptions,
) => {
  let lastUploadedSize = 0 // 上次断点续传时上传的总大小
  let uploadedSize = 0 // 已上传的大小
  const totalSize = file.size || 0 // 文件总大小
  let startMs = new Date().getTime() // 开始上传的时间
  const {
    exitPartList,
    chunkSize,
    chunkNum,
    fileIdentifier: identifier,
  } = taskRecord
  // 获取从开始上传到现在的平均速度（byte/s）
  const getSpeed = () => {
    // 已上传的总大小 - 上次上传的总大小（断点续传）= 本次上传的总大小（byte）
    const intervalSize = uploadedSize - lastUploadedSize
    const nowMs = new Date().getTime()
    // 时间间隔（s）
    const intervalTime = (nowMs - startMs) / 1000
    return intervalSize / intervalTime
  }

  const uploadNext = async (partNumber: number) => {
    const start = chunkSize * (partNumber - 1)
    const end = start + chunkSize
    const blob = file.slice(start, end)
    try {
      const { data } = await reqPreSignUrl(identifier, partNumber)
      if (data) {
        await httpExtra.request({
          url: data,
          method: 'PUT',
          data: blob,
          headers: { 'Content-Type': 'application/octet-stream' },
        })
        return Promise.resolve({
          partNumber: partNumber,
          uploadedSize: blob.size,
        })
      }
    } catch (error) {
      return Promise.reject(`分片${partNumber}， 获取上传地址失败`)
    }
  }

  /**
   * 更新上传进度
   * @param increment 为已上传的进度增加的字节量
   */
  const updateProcess = (increment: number) => {
    const { onProgress } = options
    let factor = 1000 // 每次增加1000 byte
    let from = 0
    // 通过循环一点一点的增加进度
    while (from <= increment) {
      from += factor
      uploadedSize += factor
      const percent = Math.round((uploadedSize / totalSize) * 100).toFixed(2)
      onProgress({ percent: percent } as any)
    }

    const speed = getSpeed()
    const remainingTime =
      speed != 0 ? Math.ceil((totalSize - uploadedSize) / speed) + 's' : '未知'
    console.log('剩余大小：', (totalSize - uploadedSize) / 1024 / 1024, 'mb')
    console.log('当前速度：', (speed / 1024 / 1024).toFixed(2), 'mbps')
    console.log('预计完成：', remainingTime)
  }

  return new Promise((resolve) => {
    const failArr: string[] = []
    const queue = Queue(5, {
      retry: 3, //Number of retries
      retryIsJump: false, //retry now?
      workReject: function (reason: string) {
        failArr.push(reason)
      },
      queueEnd: function () {
        resolve(failArr)
      },
    })
    fileUploadChunkQueue[file.uid] = queue
    for (let partNumber = 1; partNumber <= chunkNum; partNumber++) {
      const exitPart = (exitPartList || []).find(
        (exitPart) => exitPart.partNumber == partNumber,
      )
      if (exitPart) {
        // 分片已上传完成，累计到上传完成的总额中,同时记录一下上次断点上传的大小，用于计算上传速度
        lastUploadedSize += exitPart.size
        updateProcess(exitPart.size)
      } else {
        queue.push(() =>
          uploadNext(partNumber).then((res: any) => {
            // 单片文件上传完成再更新上传进度
            updateProcess(res.uploadedSize)
          }),
        )
      }
    }
    if (queue.getLength() == 0) {
      // 所有分片都上传完，但未合并，直接return出去，进行合并操作
      resolve(failArr)
      return
    }
    queue.start()
  })
}

/**
 * 1.el-upload 自定义上传方法入口
 */
const handleHttpRequest = async (options: UploadRequestOptions) => {
  isUploadingVod.value = true
  const file = options.file
  try {
    // 获取一个上传任务，没有则初始化一个
    const task = await getTaskInfo(file)
    if (task) {
      const { finished, path, taskRecord } = task
      const { fileIdentifier: identifier } = taskRecord
      if (finished) {
        return path
      } else {
        // 文件分片上传
        const errorList = await handleUpload(file, taskRecord, options)
        if ((errorList as any[]).length > 0) {
          ElNotification.error({
            title: '文件上传错误',
            message: '部分分片上次失败，请尝试重新上传文件',
          })
          return
        }
        let msg
        try {
          // 合并分片
          const { message } = await reqMerge(identifier)
          msg = message
          return path
        } catch (error) {
          ElNotification.error({
            title: '文件上传错误',
            message: msg,
          })
        }
      }
    }
  } catch (error) {
    ElNotification.error({
      title: '文件上传错误',
      message: '上传任务失败,请稍后重试',
    })
  }
}

/**
 * 移除文件列表中的文件
 * 如果文件存在上传队列任务对象，则停止该队列的任务
 */
const handleRemoveFile = async (uploadFile: UploadFile) => {
  const queueObject = fileUploadChunkQueue[uploadFile.uid]
  if (queueObject) {
    queueObject.stop()
    fileUploadChunkQueue[uploadFile.uid] = undefined
  }
  await reqDeleteVideo(vodDelObj)
}

const handleRemove = (file: UploadFile) => {
  //暂停视频播放
  videoRef.value.pause()
  //移除文件
  uploadRef.value?.handleRemove(file)
  //以确保所有相关数据都被卸载
}

/** 文件上传成功处理 */
const handleFileSuccess = (request: any) => {
  isUploadingVod.value = false
  if (request) {
    ElMessage.success('文件上传成功')
    videoRef.value.src = request
  }
}
// #endregion
</script>

<style lang="scss" scoped>
/* 章节list */
.chapter {
  cursor: pointer;
  .chapter_item {
    /* 章节 */
    .chapter_title {
      display: flex;
      align-items: center;
      background: #f5f7fa;
      padding: 13px 20px;
      border-radius: 10px;
      .catalog_num {
        display: flex;
        align-items: center;
        height: 20px;
        padding-left: 14px;
        background: #d0d5db;
        border-radius: 10px;
        margin: 0 20px 0 0;

        span {
          min-width: 16px;
          height: 16px;
          padding: 4px;
          margin: 0 -4px 0 0;
          background: #d0d5db;
          display: block;
          border-radius: 20px;
          i {
            min-width: 12px;
            height: 16px;
            padding: 0 2px;
            line-height: 16px;
            background: #fff;
            font-size: 12px;
            font-family: Arial;
            color: #a8a8b3;
            border-radius: 20px;
            display: block;
            text-align: center;
          }
        }
      }
      .catalog_name {
        display: flex;
        align-items: center;
        max-height: 48px;
        font-size: 16px;
        line-height: 24px;
        white-space: normal;
        overflow: hidden;
        text-overflow: ellipsis;
        &:hover span {
          color: #3a4b87;
        }
        .clicktitle {
          color: #131b26;
        }
      }
    }
    /* 课时 */
    .section {
      ul {
        list-style-type: none;
        li {
          padding: 10px 0;
          &:hover {
            background: rgb(240, 246, 255);
            border-radius: 10px;
          }
          .catalog_name {
            display: flex;
            align-items: center;
            padding-left: 60px;

            .clicktitle {
              flex-grow: 1;
              &:hover span {
                color: #3a8bff;
              }
            }
          }
        }
      }
    }
  }
}
/* 上传按钮加载动作 */
.el-button .custom-loading .circular {
  margin-right: 6px;
  width: 18px;
  height: 18px;
  animation: loading-rotate 2s linear infinite;
}
.el-button .custom-loading .circular .path {
  animation: loading-dash 1.5s ease-in-out infinite;
  stroke-dasharray: 90, 150;
  stroke-dashoffset: 0;
  stroke-width: 2;
  stroke: var(--el-button-text-color);
  stroke-linecap: round;
}
</style>
